mod latency;
pub mod logger;

use crate::app::AppState;
use crate::config::ServerConfig;
use crate::server::latency::LatencyOnResponse;
use axum::extract::{DefaultBodyLimit, Request};
use axum::Router;
use bytesize::ByteSize;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::time::Duration;
use tokio::net::TcpListener;
use tower_cookies::{CookieManagerLayer, Key};
use tower_http::cors;
use tower_http::cors::CorsLayer;
use tower_http::normalize_path::NormalizePathLayer;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tower_sessions::{MemoryStore, SessionManagerLayer};

pub struct Server {
    config: &'static ServerConfig,
}

impl Server {
    pub fn new(config: &'static ServerConfig) -> Server {
        Self { config }
    }

    pub async fn start(&self, state: AppState, router: Router<AppState>) -> anyhow::Result<()> {
        //1. 添加中间件到路由
        let router: Router = self.add_middleware_to_route(state, router);

        //2. 启动监听-服务
        let port = self.config.port();
        let listener = TcpListener::bind(
            SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port)
        ).await?;
        tracing::info!("Server is listening on {}",listener.local_addr()?);
        axum::serve(
            listener,
            router.into_make_service_with_connect_info::<SocketAddr>(),
        ).await?;
        Ok(())
    }

    fn add_middleware_to_route(&self, state: AppState, router: Router<AppState>) -> Router {
        let timeout_layer = TimeoutLayer::new(Duration::from_secs(120));

        let body_limit_layer = DefaultBodyLimit::max(ByteSize::mib(10).as_u64() as usize);

        let cors_layer = CorsLayer::new()
            .allow_origin(cors::Any)
            .allow_methods(cors::Any)
            .allow_headers(cors::Any)
            .allow_credentials(false)
            .max_age(Duration::from_secs(3600 * 12));


        let tracing_layer = TraceLayer::new_for_http()
            .make_span_with(|request: &Request| {
                let method = request.method();
                let path = request.uri().path();
                let id = xid::new();
                tracing::info_span!("api request", id = %id, method = %method, path = %path)
            })
            .on_request(())
            .on_failure(())
            .on_response(LatencyOnResponse);

        let normalize_path = NormalizePathLayer::trim_trailing_slash();

        let session_store = MemoryStore::default();

        let cookie_secret_key: [u8; 64] = [0u8; 64];
        let session_layer = SessionManagerLayer::new(session_store)
            .with_secure(false)
            .with_http_only(true)
            .with_same_site(tower_cookies::cookie::SameSite::Lax)
            .with_expiry(tower_sessions::Expiry::OnSessionEnd)
            .with_signed(Key::from(&cookie_secret_key));

        Router::new()
            .merge(router)
            .layer(CookieManagerLayer::default())
            .layer(session_layer)
            .layer(timeout_layer)
            .layer(body_limit_layer)
            .layer(tracing_layer)
            .layer(cors_layer)
            .layer(normalize_path)
            .with_state(state)
    }
}